﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

namespace zijian666.Http;

/// <summary>
/// 解析器: 可以尝试将 <see cref="object"/> 解析为 <seealso cref="KeyValuePair{TKey, TValue}"/>
/// </summary>
public static class KeyValuePraser
{
    private static readonly IEnumerable<KeyValuePair<string, string>> _empty = new KeyValuePair<string, string>[0];

    /// <summary>
    /// 返回解析结果
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="prefix">返回的key的前缀</param>
    /// <returns></returns>
    public static IEnumerable<KeyValuePair<string, string>> Enumerable(object obj, string prefix = null)
    {
        return obj switch
        {
            null => _empty,
            DBNull _ => _empty,
            IEnumerable<KeyValuePair<string, string>> enumerable => Enumerable(prefix, enumerable),
            IEnumerable<KeyValuePair<string, object>> enumerable => Enumerable(prefix, enumerable),
            StringDictionary dictionary => Enumerable(prefix, dictionary),
            NameValueCollection nv => Enumerable(prefix, nv),
            string str => EnumerableSingle(prefix, str),
            IConvertible convertible => EnumerableSingle(prefix, convertible.ToString(null)),
            IFormattable formattable => EnumerableSingle(prefix, formattable.ToString(null, null)),
            IEnumerable enumerable => Enumerable(prefix, enumerable),
            _ => Enumerable(prefix, obj),
        };
    }

    /// <summary>
    /// 返回解析后的键值对
    /// </summary>
    public static IDictionary<string, string> Dictionary(object obj, StringComparer comparer = null)
    {
        var dict = new InnerDictionary(comparer);
        foreach (var item in Enumerable(obj))
        {
            dict[item.Key] = item.Value;
        }
        return dict;
    }

    private class InnerDictionary : Dictionary<string, string>, IDictionary<string, string>
    {
        public InnerDictionary(StringComparer comparer)
            : base(comparer ?? StringComparer.OrdinalIgnoreCase)
        {

        }

        string IDictionary<string, string>.this[string key]
        {
            get => TryGetValue(key, out var value) ? value ?? "" : "";
            set => this[key] = value;
        }
    }

    private static string Concat(this string prefix, string name)
        => prefix is null ? name : prefix + "." + name;

    private static IEnumerable<KeyValuePair<string, string>> EnumerableSingle(string prefix, string value)
    {
        yield return new KeyValuePair<string, string>(prefix, value);
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, object obj)
    {
        foreach (var p in obj.GetType().GetProperties())
        {
            if (p.CanRead)
            {
                var value = p.GetValue(obj);
                foreach (var item in Enumerable(value, prefix.Concat(p.Name)))
                {
                    yield return item;
                }
            }
        }
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, NameValueCollection nv)
    {
        for (int i = 0; i < nv.Count; i++)
        {
            yield return new KeyValuePair<string, string>(prefix.Concat(nv.GetKey(i)), nv.Get(i));
        }
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, IEnumerable<KeyValuePair<string, object>> enumerable)
    {
        foreach (var item in enumerable)
        {
            foreach (var x in Enumerable(item.Value, prefix.Concat(item.Key)))
            {
                yield return x;
            }
        }
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, IEnumerable<KeyValuePair<string, string>> enumerable)
    {
        foreach (var item in enumerable)
        {
            yield return new KeyValuePair<string, string>(prefix.Concat(item.Key), item.Value);
        }
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, StringDictionary dictionary)
    {
        foreach (string key in dictionary.Keys)
        {
            yield return new KeyValuePair<string, string>(prefix.Concat(key), dictionary[key]);
        }
    }

    private static IEnumerable<KeyValuePair<string, string>> Enumerable(string prefix, IEnumerable enumerable)
    {
        var list = new List<KeyValuePair<string, string>>();

        foreach (var item in enumerable)
        {
            foreach (var x in Enumerable(item, prefix))
            {
                list.Add(x);
            }
        }

        foreach (var group in list.GroupBy(x => x.Key))
        {
            if (group.Count() == 1)
            {
                yield return new KeyValuePair<string, string>(group.Key, group.FirstOrDefault().ToString());
            }
            else
            {
                yield return new KeyValuePair<string, string>(group.Key, string.Join(",", group.Select(x => x.Value)));
            }
        }
    }
}
